Conversation
The staging/fed-staging intake endpoints were hardcoded to an internal IP (http://10.99.1.110:11300) directly in `intakeSites.ts`. Since these constants are publicly exported, the internal IP + plaintext http endpoint leaked into every published `@flashcatcloud/browser-*` artifact. Move the staging endpoints to the existing build-time injection mechanism (`__BUILD_ENV__*` + replace-build-env / webpack DefinePlugin), the same convention already used for SDK_VERSION. Values are read from the INTAKE_SITE_STAGING / INTAKE_SITE_FED_STAGING env vars at build time and default to an empty string, so release artifacts never contain an internal host. Staging remains configurable by setting the env var at build time. Verified: `BUILD_MODE=release` build of @flashcatcloud/browser-core emits `INTAKE_SITE_STAGING = ""` and `grep -r 10.99` over cjs/ and esm/ is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the build-time env injection with a hardcoded public https host, matching the native SDKs (Android FlashcatSite.STAGING / iOS FlashcatSite.staging both use jira.flashcat.cloud). This keeps the original audit finding resolved (no internal 10.99.x IP, no cleartext http/port) while making staging work out of the box across all builds with zero CI wiring, and keeps the three client SDKs consistent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(core): remove hardcoded internal staging IP from intake sites
Introduce packages/rum-legacy, a CDN-only bundle for browsers without ES2015 support. This commit sets up the toolchain only; collection and transport follow. The package does not extend tsconfig.base.json on purpose. Restricting "lib" to ES5 + DOM turns a missing runtime API into a compile error rather than a crash on the target browsers, and an empty "paths" map keeps @flashcatcloud/* imports unresolvable, since those packages are authored against ES2018. check-es5-compatibility.js parses the emitted bundle with acorn at ecmaVersion 5. It also asserts that the modern bundles are rejected: if a misconfiguration made the parser accept everything, the positive assertion alone would still pass and the gate would silently stop protecting anything. Console access is looked up lazily instead of captured at module evaluation, because in IE9 window.console does not exist until the developer tools are opened, and its methods are host objects without bind().
Transport for browsers that have neither fetch nor sendBeacon. The intake url is built to match the modern bundle byte for byte, so a single reverse proxy rule on the customer domain serves both builds and the intake needs no compatibility branch. Two details carry that property and are easy to get wrong: the real intake path travels inside the ddforward query parameter rather than being appended to the proxy path, and a relative proxy value is resolved to an absolute url first. The specs build a reference url with the modern implementation and compare against it, so a change on either side fails loudly instead of drifting. Completion is detected through onreadystatechange. onload arrived in IE10, so a transport built on it would look correct in a modern test browser and never complete on the browsers this package exists for. The spec's fake XMLHttpRequest fires only onreadystatechange to keep that honest. The exit path sends synchronously because there is no sendBeacon to hand the payload to. Batch limits match the modern bundle. Payload size is measured with a UTF-8 byte count rather than string length, which would undercount non-latin content threefold and let batches grow past the intake limit. Session identity reuses the modern cookie name, serialisation and expiration rules. The modern parser rejects uppercase characters, so the generated uuid has to stay lowercase or every page load would silently start a new session. Timers and listener registration go through the unpatched originals when Zone.js is present, whose patched versions have been observed to cause memory leaks and high CPU usage in host pages. Time is read via a local dateNow() rather than Date.now(), which some sites wrongly polyfill to return a Date instance. Pages still running these browsers are the most likely to carry such a dependency.
Adds the event assembly and the collection this build can actually support: uncaught errors, page load timings, view lifecycle and manual actions. Event shape is validated in the specs against the shared rum-events-format schemas rather than against hand-written expectations, since the intake owns that format. Durations are nanoseconds, so page load timings derived from performance.timing are converted rather than passed through as milliseconds. The zero-valued resource and long task counts are emitted rather than omitted. Those signals cannot be observed on these browsers, and leaving the fields out would read downstream as missing data instead of a real zero. Timings the browser has not reached are the opposite case: performance.timing reports them as 0, which would be a false measurement, so they are left out. window.onerror preserves and still calls whatever handler the page had installed, and passes its return value back so the page can keep suppressing the browser's default logging. Replacing it outright would silently disable the customer's own error reporting. Without an error object there is no stack, so the script url and line are folded into a single synthetic frame, which is what makes the error locatable at all. Route changes are tracked through hashchange only, as there is no History API to hook into here.
Wires collection, assembly, batching and transport behind the same FC_RUM surface the modern bundle exposes. Methods that cannot be supported here are no-ops rather than absent. There is no PerformanceObserver for vitals, no MutationObserver for session replay and no way to observe resource timings, but a missing method throws "undefined is not a function" and takes the host page down, which is the failure this package exists to prevent. A page written against the modern bundle therefore runs unchanged. Every public method is wrapped so an internal failure cannot surface as an exception in the page. onReady is deliberately left unwrapped: it invokes the caller's own callback, and swallowing there would hide the customer's exceptions rather than ours. The view context is passed to the view update callback rather than read back from the manager. The first update is emitted while the manager is still being constructed, so reading it back threw and, being caught by the safety net, silently produced no events at all. Uncaught and manually added errors share one path, so an error is counted and reported exactly once. The bundle size grows from 505 bytes to 39 KiB of sources, still parsing as ES5.
Adds a fixture that removes fetch, Promise, sendBeacon, the observers, TextEncoder and the URL constructor, then drives the package end to end through an XMLHttpRequest offering only onreadystatechange. Without it every spec runs in a browser that has all of those, so a dependency on one would pass the suite and fail only where this package is meant to run. The ES2015 collections are deliberately left in place. lib: ES5 already makes using them a compile error, a stronger guarantee than a runtime spec, and the bundle scan covers the emitted output. Removing them here broke the suite's own instrumentation instead: the shared leak detector wraps addEventListener in a function that constructs a Map, so the first listener this package registered failed inside the harness rather than inside the code under test. Globals are restored by putting back the captured property descriptor, and a shadow over an inherited property is deleted rather than overwritten. Restoring navigator.sendBeacon by assignment left it as an own property of the instance rather than a method on Navigator.prototype, which changed its shape for every later spec in the same browser context and failed 41 of them across other packages. check-es5-compatibility.js now also scans the bundle for runtime APIs the target browsers lack. Parsing as ES5 says nothing about those: a bundle full of Promise and fetch parses perfectly well and then fails on the first line that runs. Adds a package README covering setup, the required same-origin proxy, the capability matrix, and an explicit statement that this has not been verified on real hardware.
The merge and empty-check loops existed twice, byte for byte, in event assembly and in the public api, because Object.assign and the spread operator both need ES2015 and lib: ES5 rejects them. They move to tools/objectUtils.ts. The block reading a message, name and stack off an Error instance also existed twice in error collection, once for uncaught errors and once for manually added ones. No behaviour change.
The view event carrying the time spent and the error and action counts was only sent when the session was stopped explicitly. On a normal page close nothing closed the view, so every view reached the intake with the counts and duration it had at page load, which are zero. Error events themselves were unaffected; the view level aggregates were not. Emitting it was not enough on its own. The batch registered its own exit listener when it was created, before the view manager existed, so it always ran first and flushed an empty buffer before the closing update could be added to it. Page exit is now owned in one place, which closes the view and then flushes, and the batch no longer listens for it. The exit path closes the view without shutting collection down. beforeunload can fire for a navigation the user then cancels, and tearing down there would leave the page with a dead SDK. It also runs once per page: the request it makes is synchronous, and blocking a closing browser twice is worse than missing a second closing update on a cancelled navigation. viewManager.flush() is replaced by endView(). It had no caller outside its own specs.
Both options were accepted, validated and then ignored. sessionSampleRate only reached _dd.configuration.session_sample_rate. Every session was collected in full while each event claimed to have been sampled at the configured rate, so the volume was wrong and the reported rate described something that never happened. The decision is now made once when a session starts and carried in the session cookie's rum field, using the same values as the standard bundles, so a session is either collected whole or not at all rather than losing a fraction of each one. trackingConsent was a no-op, which is worse for a consent control than not offering it: a page could set 'not-granted' and still be collected from. Collection now runs only while consent is exactly 'granted', matching the standard bundles, where an unrecognised value counts as not granted. Withdrawing consent drops whatever is buffered instead of sending it and clears the session cookie. Session cookie access is throttled to one second, as the standard bundles throttle it. The session is looked up for every event, and reading and writing document.cookie is a full string parse each time, which is a cost worth avoiding on the browsers this package targets.
Public methods were wrapped so an internal failure could not surface in the host page, but the handlers the browser calls back into were not. A failure inside the hashchange, load or page exit listener became an uncaught error on the page, which is the outcome this package exists to avoid. The wrapper moves to tools/monitor.ts and now covers both entry points. Removing the wrapper failed no test before this change, so the guard was untested rather than merely missing; the specs added here fail without it. Making them fail for the right reason also required advancing past the new session cookie throttling window, since a page that exits within a second of init never touches the cookie and never reaches the injected failure. Views started in-page reported document.referrer, which describes how the document was reached rather than how the view was, attributing every in-page navigation to whatever site linked to the page. They now report the previous view's url, as the standard bundles do, and only the first view of a document falls back to document.referrer. The loader snippet stubs init so that calling it outside onReady, before the script has landed, queues the call instead of throwing "undefined is not a function". The README also records where this build's stopSession and setViewName deliberately differ from the standard bundles. Session cookies are cleared before each spec as well as after: a spec elsewhere may leave one behind, and a stale session would be reused instead of a fresh one being created.
…ion reuse Five more review passes, over clock behaviour, ordering, hostile input, release plumbing and drift between the docs and the code. Durations come from the wall clock, because these browsers have no monotonic performance.now(). A backwards clock correction made time_spent negative, which is not a measurement but a broken one, and made the session throttle read the negative elapsed time as "still inside the window", freezing the session until the clock caught up. Elapsed time is now floored at zero and the throttle treats a backwards jump as an elapsed window. The page exit produced the closing view update and then flushed. If that update crossed a buffer limit it started an asynchronous request, which a closing page never completes. The update is now produced inside the exit flush, so the whole sequence stays on the synchronous transport. A session started by the standard bundles with session replay sampled is stored as '1' rather than '2'. Reading only '2' as tracked meant such a session was treated as sampled out and silenced for its whole lifetime. Both builds share one cookie jar per domain, and IE enterprise site lists routinely put some urls of a site in compatibility mode and others not, so this is reachable rather than theoretical. Hostile input was probed rather than assumed: a crafted session cookie, a polluted Object prototype and a malformed cookie value are all contained already, and now have specs saying so. The bundle whose whole purpose is being small was missing from the size report. It is 4 KiB gzipped. The README claimed the degraded environment specs remove Map, Set and Symbol. They deliberately do not, and overstating the coverage is worse than describing it narrowly.
Four more review passes, over the emitted artifact, the module surface, the changes outside this package, and the public API semantics. The suite never executed the file customers actually load. Every spec runs against TypeScript compiled by the test runner, and between that and the shipped bundle sit Terser and the webpack runtime. A new check executes the emitted file in an environment with no fetch, no Promise, no sendBeacon and an XMLHttpRequest that only fires onreadystatechange, then asserts what lands on the wire, including the intake path and parameters carried inside ddforward. It found a real defect on its first run. Errors were recognised with a bare `instanceof Error`, which compares against the current frame's constructor, so an error created in another frame was treated as a plain value and stringified, losing its message, type and stack. Frameset and iframe heavy applications are the norm on these browsers. The standard bundles allow for this and now so does this one, verified with a real iframe rather than a simulation. The getters handed out the objects the SDK keeps rather than copies. The stored configuration is what a later consent grant starts from, and the contexts are attached to every event, so a caller could change SDK behaviour by mutating what it read. computeBytesCount and normalizeUrl were exported without a consumer, which reads as part of the module's contract when they are internal. The root build, the deploy path's package list and the workflow's ES5 step were run end to end rather than assumed.
The previous pass stopped the getters handing out the objects the SDK keeps, but left the other half: setGlobalContext, setUser, setAccount and init all stored the caller's object by reference. Pages commonly keep the object they passed. An unrelated later mutation of it silently changed what every subsequent event carried, and for the configuration it changed what a later consent grant would start from. Fixing only the read side left the same defect reachable from the write side, which is worse than not having noticed it, because the specs looked like the problem was covered. Data is now copied at both boundaries.
…e fields The sample rate range was checked by negating it. NaN fails every comparison, so a rate computed from a string and landing on NaN passed validation, and then failed the sampling comparison too: the SDK looked configured and silently reported nothing, which is the worst way for a monitoring build to go wrong. The range is now checked positively, as the standard bundles check it. The session cookie is shared with the standard bundles, which keep their own entries in it. The anonymous user id is one of them and is tracked by default. Rewriting the cookie with only the four fields this build understands destroyed it, so a visit through a page served in compatibility mode reset anonymous user continuity for every other page of the same site. Entries this build does not understand are now written back untouched; they still cannot reach the session identity or the tracking decision, which are read from named fields only.
Verified the "no backend change" claim against the intake itself rather than against the standard bundles' url shape, and found the transport would have been refused outright. The intake rejects any body whose content type is not text/plain. This build deliberately set no request header at all, on the reasoning that it kept the request simple and avoided a preflight. Both halves of that were wrong: a same-origin request never preflights, and text/plain is a safelisted value that does not trigger one even cross-origin. The standard bundles get away with declaring nothing because fetch and sendBeacon set it implicitly for a string body; XMLHttpRequest on these browsers cannot be relied on to do the same. Nothing client-side could have caught this. The specs and the artifact check both asserted the absence of headers, so the mistaken belief was encoded three times over: in the transport, in its spec, and in the fake XMLHttpRequest of the degraded environment specs, which threw if a header was set. Both levels now assert the header, and both fail without it.
Everything this package is checked with so far runs on a modern engine: the unit suite, the degraded-environment specs and the artifact smoke test all approximate the target browsers rather than being one. This adds the missing step, a harness for running the shipped bundle on a real browser and seeing the result on the device itself. The page is plain ES5 and renders every check into the DOM, because the browsers it targets often have no usable developer tools. The server doubles as a same-origin intake that records what actually arrived, so the checks assert the wire rather than the SDK's own claims: the bundle loads, the collection APIs do not throw into the page, an uncaught error still reaches the page's own handler, the session cookie is written, and the intake received a text/plain POST whose real path travels inside ddforward. One check only has teeth on an old engine: any fetch-era browser adds the content type to a string body implicitly, so the header assertion cannot fail there regardless of the SDK. That is exactly why it lives in this harness and not only in the unit suite. JSON is parsed with JSON.parse, native since IE8. An eval-based parse would also break under any Content Security Policy, which the rest of the package promises not to require.
Cloud device farms meter free sessions by the minute, and the harness spent over thirty seconds waiting out the SDK's flush timer. The run now fills the batch to its limit so it flushes over the asynchronous path immediately, starts itself when opened with ?autorun=1, and keeps its results across the exit-check reload in sessionStorage. A full pass takes under a second plus one reload. The root path did not resolve when a query string was attached, which made ?autorun=1 a 404: routing now matches on the pathname. The page-exit check reports SKIP rather than FAIL on modern engines, which block synchronous XHR during page dismissal by design. Like the content-type check, it can only genuinely pass or fail on Trident, which is why it is in this page at all.
Real-device runs surfaced what happens below IE9: the loader snippet routes every browser without fetch and Promise to this bundle, and on IE8 the whole evaluation died on Object.defineProperty, which rejects plain objects there. The throw surfaced as an uncaught error in the hosting page, and IE8 document mode is routinely forced by enterprise site lists, so this is reachable, not theoretical. The promise for those engines inverts: collecting nothing is fine, but the page must stay untouched. The defineProperty call now falls back to a plain assignment, the entire module evaluation is guarded so any construction failure leaves the loader's queued stub in place, and a spec holds the constructor to that with a throwing defineProperty. Syntax cannot be guarded at runtime, so the build gate now also parses the bundle for ES3 reserved words used as property names, which the IE6/7 engines fail to parse outright. ES5 allows them, meaning neither the compiler nor the ES5 parse check would object.
Real IE runs found four defects in the harness itself. Tables are now built with DOM calls: IE9 makes innerHTML read-only on table sections and IE8 rejects it with its own error, so string rendering worked everywhere except on the devices this page exists for. The bundle loader guards its callback, because IE10 and 11 fire both onload and onreadystatechange and every check ran twice. Payload assertions aggregate all received requests, since async flushes travel the tunnel independently and arrive out of order. A rerun stops the previous SDK instance first instead of leaving two instances reporting at once. Errors now surface into the page from a separate script block that survives a syntax error in the main one, which is what identified every failure above on consoleless browsers. A final check asserts that no unexpected uncaught error reached the page, which is the whole acceptance criterion for engines below the support floor. The server logs each request so the device's traffic is observable from the serving side.
… harness Three more findings from real IE6 and IE8 runs. The formatter added trailing commas to multiline literals. They are legal ES5, so every static check passed, but IE8 counts a trailing comma in an array literal as one more undefined element, and IE6 and 7 refuse to parse them in object literals at all. The page is now listed in .prettierignore, carries a comment saying why, and the commas are gone. IE6 predates the native XMLHttpRequest constructor, so the harness's own requests threw before they could observe anything. It now falls back to the ActiveX flavour, which ran successfully on a real MSIE 6.0. The no-unexpected-errors check only rendered when the intake had received something. On engines below the support floor nothing ever arrives, and that check is precisely the acceptance criterion there: it now renders on both paths, and the closing note explains that red collection rows plus a green cleanliness row is the expected shape.
Self-hosting environments need a complete, coherent copy of the deployed bundles, and the standard RUM bundle references hash-named chunk files that are error-prone to collect by hand. The script downloads every entry bundle by name and recovers the chunk names from the chunk table webpack embeds in each entry bundle, over plain HTTPS with no credentials, and fails loudly when any file is missing. The bucket layout and the entry filenames move into deploymentUtils.js so the upload and download sides share one definition.
The README pointed at a placeholder static host without saying where released bundles actually live, and still claimed the package had never been verified on a real browser engine. Document the CDN layout and the sync-bundles workflow for self-hosting, and record the real-browser verification outcome: IE 9, 10 and 11 pass every check in the verification page, and IE 6 and IE 8 degrade to a silent no-op.
The cookie was written percent-encoded. The modern bundle reads document.cookie without decoding it, and an encoded value fails its validation, so a session started here was discarded rather than shared with a page that loads the standard bundle. Cookies already issued stay readable: the read path keeps decoding. The spec meant to catch this decoded the value before handing it to the modern parser, so it validated a string that never exists in the browser and passed on a cookie the modern bundle rejects. Removing the decode makes it fail against the old implementation. An untracked session is no longer treated as invalid either. The modern bundle only mints an id once a session is tracked, so rum=0 with no id is what a sampled-out session looks like, and renewing on a missing id re-ran the sampling draw on a session that had already been sampled out. Entries this build does not understand now survive a renewal too.
Four behaviours diverged from the standard bundle in ways that lose or misreport data: init overwrote a tracking consent the page had already set. A consent management platform commonly answers before init runs, and the answer is the user's; the configuration only supplies a default for a page that has not answered. The page exit guard was never released, so a cancelled navigation left it set and the real exit that followed did nothing: everything recorded after the cancellation went with the page. It is now released by the next event, which only a page that is still recording produces. View events were dated when the update was assembled rather than when the view started, so the closing update appeared to have happened at the moment the page was dismissed. Navigation Timing is read off window rather than as a bare identifier. Where the property is absent entirely a bare reference throws instead of evaluating to undefined, and this runs inside the first view emitted during init. The degraded environment suite now deletes the global rather than defining it as undefined, which is the only form of the hazard that reproduces it.
The loader chose the standard bundle whenever Promise and fetch were present. Those are the two most commonly polyfilled APIs on the pages this build targets, and a polyfill supplies the API without supplying the syntax, so a polyfilled IE9 was handed a bundle it cannot parse and collected nothing. document.documentMode is checked first: only Trident defines it, it reports the mode the page is actually rendered in, and no polyfill sets it. The snippet also carried trailing commas, which an ES3 parser rejects outright — in a snippet whose whole job is to route browsers that parse that way. A build gate now parses every documented snippet as ES3, since a formatter reinserts the comma given the chance. The page load timings row is marked as uneven rather than supported: on the IE9 device used for verification none were reported, while the same code fills them in on a modern browser.
Running an install re-resolved the @alicloud subtree, which has nothing to do with this branch: those packages are pinned to the floating latest range, so any install moves them. Only the acorn descriptor is kept.
…ndle startView ignores everything but the name, and a relative proxy resolves against the document base url rather than the page url. Both are visible to a page being ported and neither was written down.
The checks never looked at page load timings, which is why the IE9 run passed every row while reporting none of them. The new row separates the two explanations, which call for opposite responses: a browser without usable Navigation Timing has nothing to report and says so, while one that has it and still sends no timings is a bug here. The environment box now shows what the browser actually offers, so a single run settles which case it is. The session cookie row also stops printing the whole cookie jar. This page runs inside the customer's own environment and its results get screenshotted; the rest of document.cookie belongs to whatever else is served from that host.
Measured on a real IE9 rather than inferred from an earlier run that showed none: window.performance and performance.timing are both present there, and all five timings reach the intake. The defensive read stays, because the engines below IE9 do not have the API at all.
The comment claimed the guard stops the synchronous request from being sent twice, which is not what it does: an event arriving between beforeunload and unload releases it and buys a second request. That is the intended trade rather than a hole - the guard falls only when there is new data an exit would have to carry, so the second request is what delivers that event instead of losing it. Written down so the next reader does not restore the one-shot guard, and the data loss with it.
stopSession tore the whole pipeline down, so a page that called it never recorded again. It now ends the session and leaves collection running: the next event opens a new session with a fresh sampling draw, which is what the standard bundles do. The current view carries on, since there is no session renewal signal here to hang a new one off. init used `running` as its initialised flag, but nothing is running between init and a consent grant, so a second init in that window replaced the configuration the first one was waiting on and events went to the wrong application. A separate flag closes the window; a rejected configuration still leaves the SDK uninitialised so a corrected call works. The lockfile also lost this package's workspace entry when an unrelated re-resolution was stripped out of it, which broke `yarn workspace @flashcatcloud/browser-rum-legacy build:bundle` on a clean checkout. Restored, without the unrelated churn. The README said the package had never run on real hardware, three paragraphs after describing what it did on real hardware.
setViewName started a fresh view, which invents a navigation the user never made and splits the view's counts across two ids. An update has already gone out under the old name and cannot be retracted, but every update of a view shares its id and the intake keeps the highest document version, so renaming in place lands the new name without the phantom page view.
Three ways the script could hand over something incomplete without saying so: A thrown fetch — a refused connection or a DNS failure, which is the expected shape of trouble for the networks this script exists to serve — escaped the per-file handling, so the run stopped at the first one and the operator got a stack trace instead of the list of what was missing and the warning not to deploy it. Every file is attempted now and all failures are reported together. An interrupted run left a half-written directory that looks exactly like a finished one: an entry bundle without its chunks is unremarkable on disk. Files now land in a directory named for being unfinished and are moved into place only once every one of them is there. An existing output directory is refused rather than merged into, which would have mixed in the chunks of an older version. A chunk whose name webpack had to quote did not match the pattern that reads the chunk table, so it would have been missing from the output without ever being attempted, and so without ever being reported. Both shapes match now, and the count of names read is checked against the count in the table.
The sync script only reads what the release publishes, so it had no business restructuring how the release works to get there. Sharing one definition of the bucket layout would keep the two from drifting, but it bought that by editing production release tooling from a read-only tool, and the layout it needs is three constants. deploy-oss.js and lib/deploymentUtils.js go back to what they were. The script now depends on nothing from the release path and carries its own copy of the host, the directories and the entry filenames.
A body that stops arriving mid-download, or a disk that fills while it is being written, escaped the per-file handling: only the request itself was covered. The run ended on the first one with a stack trace, the remaining files were never attempted, and the operator never saw the list of what was missing or the warning not to deploy it - the same outcome the request handling was added to prevent. Reading and writing the body are inside the same net now. An output directory given with a trailing slash put the staging directory inside it, where the rename cannot land. Every file downloaded successfully and was then stranded in a hidden directory, and since the output directory now existed, the guard refused every re-run. The path is resolved before the staging name is derived from it. A bundle that plainly loads chunks but whose chunk table does not match the pattern now fails loudly. It used to be indistinguishable from a bundle with no chunks at all, so a webpack or terser change to the emitted runtime would have quietly produced a directory missing the very files that are hardest to notice missing. Chunk names decide where this writes and are read out of a downloaded file, so anything resolving outside the output directory is refused.
The state the modern bundle writes when a session ends is neither an id nor a tracking decision: it is an expiry marker beside the anonymous user id it deliberately carries across the expiry. This build required one of its own fields to be present before it would accept a cookie at all, so it threw that state away and reset an identifier the other build was keeping. Any parsed entry is enough now. The expiry marker is also a known field rather than a foreign one. Carried forward as foreign it would have ridden into every session this build writes, and the modern bundle reads any session carrying it as expired — a new session on every page load. Fixing the parsing without this would have been worse than leaving it broken. Ending a session now writes that same expired state instead of deleting the cookie, so what outlives a session survives it here too. Withdrawing consent still deletes the cookie outright: there the identifier is the thing being withdrawn. Withdrawing consent also stopped sending. Tearing the pipeline down buffers one last view update, and the buffer sends itself as soon as an event would take it past its size limit, so a withdrawal with a nearly full buffer put everything collected before it on the wire immediately after. The batch is stopped first now, and the spec fills the buffer to the edge rather than trusting a small one.
… does What outlives a session stays in this cookie once the session ends - the anonymous user id among them - but the cookie itself was written to expire with the session, fifteen minutes out. A visitor coming back twenty minutes later found it gone, which resets the identifier the modern bundle keeps for a year and which the previous commit went to some trouble to preserve. The cookie now persists for that same year. How long a session lasts is unchanged: that is decided by the expire entry inside the value, not by the attribute. The expiry attribute cannot be read back from document.cookie, so the spec captures the write instead - which is why nothing caught this.
feat(rum-legacy): ES5 build for browsers without ES2015 support
…docs feat(deploy): CDN bundle sync script and self-hosting docs
The two lines had drifted far apart: publish carries the releases (npm is at the version it holds, and the release tags are on it) along with the repairs that keep a release working, while main had the newer feature work. Reconciling them makes either branch a place you can release from and stops the next person guessing which one is true. Three files needed a decision. The deploy workflows exist on both sides: publish rebuilt them into a preflight job that validates the tag against the package version and hands a verified bundle to the deploy job, which is strictly better than what main had, so that shape wins. The ES5 compatibility gate main added is put back into it, after the build, where every bundle exists and the check that the modern bundles are rejected still means something. The lockfile is regenerated rather than kept as the text merge left it - a merged lockfile is not a lockfile, and the merged one failed --immutable. One spec still carried a hardcoded internal staging address, which is also why it failed once the two sides met: the host had been scrubbed from the source on one side and not from the fixture on the other. It derives the host from the constant now, so the next change to it cannot leave the fixture behind. The bundle sync script and its documentation followed the old layout, where a release overwrote the directory of its major version. The pipeline this merge adopts publishes each release to its own directory, so a url pins the version it names, and the script defaults to the full version.
Merging the two lines brought a CI workflow main did not have, and it runs checks main never ran. Two of them fail, both because this package was added while nothing was looking: The package metadata check requires an .npmignore per package, and this one had none. It follows the convention the others use — exclude everything, then name what ships — which here is the bundle alone. The deploy and source map specs still described three packages. Adding a fourth changed what those scripts do without changing what the specs expected of them, so every one of them failed. They now expect the legacy bundle to be uploaded, renamed and invalidated alongside the others. This was raised in review earlier and set aside because these specs did not run anywhere. They do now.
The previous attempt inserted the legacy command inside the object that held the slim one, which is two strings in a row and parses as nothing. Each expectation is now copied as a whole and rewritten.
The repository-wide typecheck compiles the specs of every package together, which main's CI never did. Two things only show up there: A spec elsewhere augments the global Window with its own type for the FC_RUM property, so an interface here extending Window with a different type for it is two declarations of one global disagreeing. This one no longer extends Window; it only needs somewhere to put the api. Two event assembly fixtures predate the view start time being part of a view's context and had not been given one. The source map expectations also had the legacy bundle renamed between the slim bundle's script and its source map, rather than after both. The renames happen a package at a time.
The fixture gained a start time, which dates the event rather than travelling inside the view object, so comparing the whole fixture to what was assembled started failing.
The clock was frozen after the view had already opened, so the view read the real wall clock for its start and the test read a frozen one two seconds later - a millisecond between the two lines was enough to fail it. It froze once on a CI runner and would have again.
…o-main Merge the publish branch into main
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The other half of reconciling the two branches.
publishwas merged intomainfirst, so this direction carries no conflicts — it brings the release line up to everythingmainnow holds, including the ES5 build for browsers without ES2015 support and the bundle sync script.After this, the two branches hold the same tree and a release can be cut from either one.